fix: document ESM resolution behavior for validator and similar packages (#189)#193
Conversation
acf79f1 to
7f935ff
Compare
|
/bigbonk review please |
There was a problem hiding this comment.
Review Summary
This PR adds useful functionality: auto-detecting problematic ESM packages and propagating ssr.noExternal to the RSC environment, which fixes a real issue (#189). The test coverage is thorough and the core approach (merging user config with auto-detected packages, propagating to both rsc and ssr environments) is sound.
However, there are several issues that need to be addressed:
Must Fix
package-lock.jsonmust not be committed — this is a pnpm monorepo. An npm lockfile is incorrect.- Wrong GitHub URL in code comment — references
nicepkg/vinextinstead ofcloudflare/vinext. - Unrelated changes bundled in — The
Space_Groteskexport (issue #190) and its test should be in a separate PR. This PR claims to fix #189 only.
Should Fix
- Questionable package list —
cuidandnanoiddon't have the described ESM issue.uuidv9+ ships proper ESM. Onlyvalidatorhas a verified reproduction. - RegExp items silently dropped — Vite's
resolve.noExternalsupports RegExp; the comment claiming otherwise is inaccurate.
Minor
- Fixture uses wrong Vite version —
^6.0.0instead of the project's^7.3.1. - Documentation references unverified version (
v0.0.16+).
package-lock.json
Outdated
| @@ -0,0 +1,5958 @@ | |||
| { | |||
There was a problem hiding this comment.
Must remove. This is a pnpm monorepo (uses pnpm-lock.yaml). A 5,958-line npm package-lock.json should not be committed — it's the wrong package manager's lockfile and will cause confusion.
packages/vinext/src/index.ts
Outdated
| * Adding them to `resolve.noExternal` forces Vite to bundle them through | ||
| * its transform pipeline, which resolves extensionless imports correctly. | ||
| * | ||
| * See: https://github.com/nicepkg/vinext/issues/189 |
There was a problem hiding this comment.
Wrong repository URL. The repo is cloudflare/vinext, not nicepkg/vinext.
| * See: https://github.com/nicepkg/vinext/issues/189 | |
| * See: https://github.com/cloudflare/vinext/issues/189 |
packages/vinext/src/index.ts
Outdated
| "validator", // validator/es/lib/isEmail.js can't resolve ./util/assertString | ||
| "uuid", // Similar ESM resolution issues | ||
| "date-fns", // date-fns/esm has complex internal structure | ||
| "cuid", |
There was a problem hiding this comment.
cuid and nanoid do not have the ESM resolution issue described in the JSDoc comment above. Both ship clean ESM with proper exports maps. Adding packages speculatively to this allowlist forces Vite to bundle them through its transform pipeline unnecessarily, which:
- Increases dev server startup time
- Can break packages that rely on being externalized
- Erodes trust in the list (users will question every entry)
uuid v9+ also ships proper ESM with file extensions. The comment "Similar ESM resolution issues" is unverified.
Recommendation: Only include packages with verified reproduction cases. Keep validator (verified in #189) and date-fns (known complex ESM internal structure). Remove cuid, nanoid, and verify uuid before including it.
packages/vinext/src/index.ts
Outdated
| } else if (Array.isArray(userSsrNoExternal)) { | ||
| for (const item of userSsrNoExternal) { | ||
| if (typeof item === "string") packages.add(item); | ||
| // RegExp items are skipped — can't propagate to resolve.noExternal |
There was a problem hiding this comment.
The comment says RegExp items "can't propagate to resolve.noExternal" — this is inaccurate. Vite's resolve.noExternal does support RegExp values (it accepts string | RegExp | (string | RegExp)[] | true). If you're choosing to skip them for implementation simplicity, the comment should reflect that, e.g. "RegExp items are not propagated — only string package names are supported".
| export const JetBrains_Mono = createFontLoader("JetBrains Mono"); | ||
| export const Geist = createFontLoader("Geist"); | ||
| export const Geist_Mono = createFontLoader("Geist Mono"); | ||
| export const Space_Grotesk = createFontLoader("Space Grotesk"); |
| }, | ||
| "devDependencies": { | ||
| "vinext": "workspace:*", | ||
| "vite": "^6.0.0" |
There was a problem hiding this comment.
The project uses Vite 7 ("vite": "^7.3.1" in root package.json). This fixture should match the workspace version.
| "vite": "^6.0.0" | |
| "vite": "^7.3.1" |
| ``` | ||
|
|
||
| **Cause:** Some ESM packages have complex internal import structures that Node.js module resolution can't handle when externalized. By default, Vite externalizes dependencies in SSR (loads them via Node.js) instead of transforming them through Vite's pipeline. | ||
|
|
There was a problem hiding this comment.
Where does v0.0.16+ come from? This version number should match the actual release version where this feature will ship. If uncertain, remove the version reference to avoid documenting an inaccurate version number.
|
|
||
| export default defineConfig({ | ||
| plugins: [vinext()], | ||
| // No manual ssr.noExternal needed - vinext auto-detects validator! |
There was a problem hiding this comment.
"auto-detects" is somewhat misleading — it's a hardcoded allowlist check, not runtime detection of ESM issues. Consider:
| // No manual ssr.noExternal needed - vinext auto-detects validator! | |
| // No manual ssr.noExternal needed - vinext includes validator in its known problematic ESM packages list |
|
The review was submitted successfully. Here's a summary of what I flagged: Review Posted on PR #193Verdict: Changes Requested Must Fix
Should Fix
Minor
|
02b9d86 to
69cdd65
Compare
|
@elithrar @southpolesteve kindly review the changes. |
|
kindly review the changes. |
1d83370 to
b7b5402
Compare
commit: |
b7b5402 to
e476e70
Compare
|
@southpolesteve The workflow is all green. |
6b707a7 to
cfb7c8a
Compare
|
/bigbonk re-review this pr please |
There was a problem hiding this comment.
Review
This PR has been significantly cleaned up since the previous review — the problematic package-lock.json, Space_Grotesk changes, speculative package allowlist, and wrong GitHub URL are all gone. What remains is a focused, minimal PR: 6 lines of explanatory comments in index.ts, a troubleshooting doc section, and a validator ecosystem test fixture.
All CI checks pass. The changes are sound.
I have a few minor notes below, but nothing blocking.
|
|
||
| **Cause:** Some ESM packages have complex internal import structures that Node.js module resolution can't handle when externalized. | ||
|
|
||
| **vinext Fix:** vinext sets `noExternal: true` in all server environments (RSC and SSR), which forces all dependencies through Vite's transform pipeline. This resolves extensionless import issues automatically. |
There was a problem hiding this comment.
Minor: the PR title and description still say "document ESM resolution workaround" and describe adding packages to ssr.noExternal manually, but the actual doc here correctly says "No configuration needed — this is the default behavior." The PR description is stale/misleading relative to what the code actually does. Worth updating the PR description to match the actual content (documentation of existing behavior, not a workaround).
tests/ecosystem.test.ts
Outdated
| let fetchPage: (pathname: string) => Promise<{ html: string; status: number }>; | ||
|
|
||
| beforeAll(async () => { | ||
| const fixture = await startFixture("validator", 5011); |
There was a problem hiding this comment.
Nit: the existing fixtures use sequential ports 4400–4404. Port 5011 is a noticeable jump. Consider using 4405 to keep the sequence consistent and make it easier to spot conflicts.
| const fixture = await startFixture("validator", 5011); | |
| const fixture = await startFixture("validator", 4405); |
tests/build-optimization.test.ts
Outdated
| expect(result).not.toContain("a;b"); | ||
| }); | ||
| }); | ||
|
|
There was a problem hiding this comment.
This file's only change is adding a trailing newline. Harmless, but appears to be an accidental edit — the file has nothing to do with the validator ESM fix. Consider dropping it from the PR to keep the diff focused.
packages/vinext/src/index.ts
Outdated
| // Skip when targeting bundled runtimes (Cloudflare/Nitro bundle everything). | ||
| // Also propagate noExternal for problematic ESM packages so they | ||
| // are bundled by Vite instead of loaded natively (where Node's | ||
| // strict ESM resolution rejects extensionless imports). |
There was a problem hiding this comment.
Nit: this comment is slightly misleading in context. The noExternal: true on line 2068 forces all dependencies through Vite, not just "problematic ESM packages." The existing comment block (lines 2053–2061) already explains the rationale clearly. This addendum reads like there's a separate per-package mechanism, but there isn't — it's the same blanket noExternal: true.
Consider trimming to something like:
// This also resolves extensionless-import issues in packages like
// `validator` (see #189) by routing them through Vite's resolver.
packages/vinext/src/index.ts
Outdated
|
|
||
| // For App Router, propagate noExternal to BOTH rsc and ssr | ||
| // environments (the top-level ssr.noExternal only affects the | ||
| // default ssr env, not the rsc env). |
There was a problem hiding this comment.
Same note as above — the comment says "propagate noExternal" as if this is a new behavior being added. In reality, lines 2154 and 2177 already had noExternal: true before this PR. The comment is purely additive documentation of existing behavior, which is fine, but the phrasing could be clearer that this is explaining why the existing code works, not describing a change.
Minor — no action required.
|
Review posted successfully. Here's a summary of what I flagged: Review Summary for PR #193Verdict: Looks good overall — all CI passes, the PR is well-scoped after cleanup. The PR adds:
Minor notes posted:
|
cloudflare#189) Vinext now automatically detects installed packages with broken ESM resolution and adds them to resolve.noExternal for both the rsc and ssr environments. This fixes 'Cannot find module' errors when using packages like validator that have extensionless internal ESM imports. Root cause: Vite externalizes dependencies for SSR by default, but some packages use extensionless imports (e.g. ./util/assertString) that fail under Node.js strict ESM resolution. Additionally, Vite 7's Environment API means ssr.noExternal only affects the ssr environment, not the rsc environment where server components run. Changes: - Add KNOWN_PROBLEMATIC_ESM_PACKAGES list (validator, date-fns) - Add collectNoExternalPackages() to merge user config with auto-detected packages - Propagate noExternal to both rsc and ssr environments - Add validator ecosystem test fixture and integration tests - Add troubleshooting documentation Fixes cloudflare#189
cfb7c8a to
75a8d4e
Compare
|
@james-elicx changes pushed. re-review the changes. |
Summary
Documents how vinext's existing
noExternal: truesetting resolves ESM resolution issues for packages likevalidator(fixes #189).Problem
Users importing certain ESM packages (like
validator) encounteredCannot find moduleerrors in dev server:How It's Already Fixed
vinext sets
noExternal: truein all server environments (RSC and SSR), which forces all dependencies through Vite's transform pipeline. This resolves extensionless import issues automatically — no user configuration needed.Changes
tests/fixtures/ecosystem/validator/to verify validator works out of the boxindex.tsreferencing Raise Cannot find module after access local dev server web page. #189